#include "CEngine.h"

static BOOL				g_bKeepAlive		= FALSE	;
static HANDLE			g_hIncomingThread	= NULL	;
static HANDLE			g_hOutgoingThread	= NULL	;
static CEngine*			g_pEngine			= NULL	;
static CRITICAL_SECTION	g_csEngineLock		= {0}	;

CEngine::CEngine()
{
	EnterCriticalSection(&g_csEngineLock);
	g_pEngine		= this;
	m_bConnected	= FALSE;
	m_pCurrentRecv	= NULL;
	m_pCurrentSend	= NULL;
	LeaveCriticalSection(&g_csEngineLock);
}

BOOL CEngine::Initialize(void)
{
	InitializeCriticalSection(&g_csEngineLock);

	g_bKeepAlive = TRUE;

	g_hIncomingThread = CreateThread(NULL, 0, 
		CEngine::IncomingPacketThread, NULL, 0, NULL);
	g_hOutgoingThread = CreateThread(NULL, 0, 
		CEngine::OutgoingPacketThread, NULL, 0, NULL);

	return (NULL != g_hIncomingThread && NULL != g_hOutgoingThread);
}

void CEngine::Uninitialize(void)
{
	EnterCriticalSection(&g_csEngineLock);
	g_bKeepAlive = FALSE;
	LeaveCriticalSection(&g_csEngineLock);

	if(NULL != g_hIncomingThread)
		WaitForSingleObject(g_hIncomingThread, INFINITE);	// Wait for thread to die.
	if(NULL != g_hOutgoingThread)
		WaitForSingleObject(g_hOutgoingThread, INFINITE);	// ^^^^^^^^^^^^^^^^^^^^^^^

	DeleteCriticalSection(&g_csEngineLock);

	memset(&g_csEngineLock, 0, sizeof(CRITICAL_SECTION));
	g_hIncomingThread = NULL;
	g_hOutgoingThread = NULL;
}

SOCKET CEngine::GetClientSocket(void)
{
	SOCKET s;
	EnterCriticalSection(&g_csEngineLock);
	s = m_ClientSocket;
	LeaveCriticalSection(&g_csEngineLock);
	return s;
}

CPacket* CEngine::GetNextPacket(void)
{
	std::vector<CPacket*>::iterator n			;
	CPacket*						pRet = NULL	;
	
	EnterCriticalSection(&g_csEngineLock);
	n = m_RecvQueue.begin();
	if(m_RecvQueue.end() != n) {
		pRet = *n;
		m_RecvQueue.erase(n);
	}
	LeaveCriticalSection(&g_csEngineLock);

	return pRet;
}

void CEngine::Disconnect(void)
{
	std::vector<CPacket*>::iterator it;

	EnterCriticalSection(&g_csEngineLock);

	m_bConnected	= FALSE;

	closesocket(m_ClientSocket);
	m_ClientSocket	= INVALID_SOCKET;

	if(NULL != m_pCurrentRecv)
		delete m_pCurrentRecv;

	if(NULL != m_pCurrentSend)
		delete m_pCurrentSend;

	m_pCurrentRecv = NULL;
	m_pCurrentSend = NULL;

	for(it = m_RecvQueue.begin(); 
		it != m_RecvQueue.end();
		++it)
		delete *it;

	for(it = m_SendQueue.begin();
		it != m_SendQueue.end();
		++it)
		delete *it;

	m_RecvQueue.clear();
	m_SendQueue.clear();

	LeaveCriticalSection(&g_csEngineLock);
}

void CEngine::Send(CPacket* pPkt)
{
	EnterCriticalSection(&g_csEngineLock);
	m_SendQueue.push_back(pPkt);
	LeaveCriticalSection(&g_csEngineLock);
}

void CEngine::_RecvProc(void)
{
	int		nRet		,
			nErr		;
	BOOL	bConnected	;

	EnterCriticalSection(&g_csEngineLock);
	bConnected = m_bConnected;
	LeaveCriticalSection(&g_csEngineLock);

	if(bConnected) {
		if(!m_pCurrentRecv)
			m_pCurrentRecv = new CPacket();

		if(m_pCurrentRecv->m_nGotten < 4) {
			nRet = recv(m_ClientSocket, m_pCurrentRecv->GetBuffer() + m_pCurrentRecv->m_nGotten,
				4 - m_pCurrentRecv->m_nGotten, 0);

			if(nRet == 0) {
				Disconnect();
				return ;
			} else if(nRet == SOCKET_ERROR) {
				nErr = WSAGetLastError();
				if(nErr != WSAEWOULDBLOCK){
					Disconnect();
					return ;
				}
			} else m_pCurrentRecv->m_nGotten += nRet;

			if(m_pCurrentRecv->m_nGotten == 4) {
				m_pCurrentRecv->Encode(0, 4);
				if(m_pCurrentRecv->GetWORD() != 0x44F4) {
					Disconnect();
					return ;
				}
				m_pCurrentRecv->m_nExpecting = m_pCurrentRecv->GetWORD();
			}
		}

		if(m_pCurrentRecv->m_nGotten >= 4) {
			nRet = recv(m_ClientSocket, m_pCurrentRecv->GetBuffer() + m_pCurrentRecv->m_nGotten,
				m_pCurrentRecv->m_nExpecting - (m_pCurrentRecv->m_nGotten - 4), 0);

			if(nRet == 0) {
				Disconnect();
				return ;
			} else if(nRet == SOCKET_ERROR) {
				nErr=  WSAGetLastError();
				if(nErr != WSAEWOULDBLOCK) {
					Disconnect();
					return ;
				}
			} else m_pCurrentRecv->m_nGotten += nRet;

			if((m_pCurrentRecv->m_nGotten-4) == m_pCurrentRecv->m_nExpecting) {
				m_pCurrentRecv->Encode(4, m_pCurrentRecv->m_nExpecting);

				EnterCriticalSection(&g_csEngineLock);
				m_RecvQueue.push_back(m_pCurrentRecv);
				LeaveCriticalSection(&g_csEngineLock);

				m_pCurrentRecv = NULL;
			}
		}
	}
}

void CEngine::_SendProc(void)
{
	int		nRet		;

	EnterCriticalSection(&g_csEngineLock);
	if(m_bConnected) {
		if(!m_pCurrentSend)
			m_pCurrentSend = _GetNextSendPacket();

		nRet = send(m_ClientSocket, m_pCurrentSend->GetBuffer() + m_pCurrentRecv->m_nSent, 
			m_pCurrentSend->GetLength() - m_pCurrentSend->m_nSent, 0);

		if(nRet != SOCKET_ERROR)
			m_pCurrentSend->m_nSent += nRet;

		if(m_pCurrentSend->m_nSent >= m_pCurrentSend->GetLength()) {
			delete m_pCurrentSend;
			m_pCurrentSend = NULL;
		}
	}
	LeaveCriticalSection(&g_csEngineLock);
}

CPacket* CEngine::_GetNextSendPacket(void)
{
	CPacket*						pRet = NULL;
	std::vector<CPacket*>::iterator n;

	n = m_SendQueue.begin();
	if(m_SendQueue.end() != n){
		pRet = *n; 
		m_SendQueue.erase(n);
	}

	return pRet;
}

void CEngine::Connect(const char* lpszHostname)
{
	struct sockaddr_in		sin			;
	hostent*				pEnt		;
	int						nTries = 0	;
	u_long					nArg = 1	;

	if(NULL != lpszHostname) {
		if(m_ClientSocket != INVALID_SOCKET)
			Disconnect();

		EnterCriticalSection(&g_csEngineLock);
		m_ClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

		if(m_ClientSocket != INVALID_SOCKET && (ioctlsocket(m_ClientSocket, FIONBIO, &nArg) != SOCKET_ERROR)) {
			do {
				pEnt = gethostbyname(lpszHostname);
			} while(!pEnt && ((++nTries) < 5));

			if(NULL != pEnt) {
				sin.sin_family				= AF_INET;
				sin.sin_addr.S_un.S_addr	= *(unsigned __int32*)(pEnt->h_addr_list[0]);
				sin.sin_port				= htons(6414);

				connect(m_ClientSocket, (struct sockaddr*)&sin, sizeof(struct sockaddr_in));
			}
		} else {
			closesocket(m_ClientSocket);
			m_ClientSocket = INVALID_SOCKET;
		}
		LeaveCriticalSection(&g_csEngineLock);
	}
}

enum CEngineConnectionState	CEngine::GetConnectionState(void)
{
	enum CEngineConnectionState ret		= IDCS_CONNECTING	;
	int							nVal						,
								nErr						;
	struct timeval				tv							;
	fd_set						wfds						;

	EnterCriticalSection(&g_csEngineLock);

	if(m_ClientSocket == INVALID_SOCKET) {
		ret = IDCS_DISCONNECTED;
	} else {

		tv.tv_sec	= 0;
		tv.tv_usec	= 5000;
		FD_ZERO(&wfds);
		FD_SET(m_ClientSocket, &wfds);
		nVal = select((int)(m_ClientSocket) + 1, NULL, &wfds, NULL, &tv);

		if(nVal > 0) {
			ret = IDCS_CONNECTED;
		} else if(nVal == SOCKET_ERROR) {
			nErr = WSAGetLastError();
			if(nErr != WSAEINPROGRESS) {
				closesocket(m_ClientSocket);
				m_ClientSocket = INVALID_SOCKET;
				ret = IDCS_DISCONNECTED;
			}
		}
	}

	m_bConnected = (ret == IDCS_CONNECTED)? TRUE : FALSE;

	LeaveCriticalSection(&g_csEngineLock);

	return ret;
}

CEngine::~CEngine()
{
	g_pEngine = NULL;
}

/////////////////////////////////////////////////////////////////////////////////
DWORD WINAPI CEngine::IncomingPacketThread(LPVOID lpParam)
{
	BOOL		bContinue	;
	CEngine*	pEngine		;
	while(TRUE) {
		EnterCriticalSection(&g_csEngineLock);
		bContinue = g_bKeepAlive;
		pEngine = g_pEngine;
		LeaveCriticalSection(&g_csEngineLock);
		
		if(!bContinue)
			break;

		if(NULL != pEngine)
			pEngine->_RecvProc();

		Sleep(10);
	}
	return 0;
}

DWORD WINAPI CEngine::OutgoingPacketThread(LPVOID lpParam)
{
	BOOL		bContinue	;
	CEngine*	pEngine		;
	while(TRUE) {
		EnterCriticalSection(&g_csEngineLock);
		bContinue = g_bKeepAlive;
		pEngine = g_pEngine;
		LeaveCriticalSection(&g_csEngineLock);

		if(!bContinue)
			break;

		if(NULL != pEngine)
			pEngine->_SendProc();

		Sleep(10);
	}
	return 0;
}
